package com.ala4.oxcafe.boot.manager;

import com.ala4.oxcafe.boot.authentication.CustomAuthenticationToken;
import com.ala4.oxcafe.boot.dto.TokenDTO;
import com.ala4.oxcafe.boot.exception.LoginUserNotFoundException;
import com.ala4.oxcafe.boot.exception.SoloLoginException;
import com.ala4.oxcafe.common.utils.id.IdUtil;
import com.ala4.oxcafe.common.utils.object.ObjectUtil;
import com.ala4.oxcafe.common.utils.text.StrUtil;
import com.ala4.oxcafe.constants.SecurityConstants;
import com.ala4.oxcafe.properties.SecurityProperties;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.userdetails.User;

import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author PING
 * @version 1.0
 * @date 2025/3/3 16:15
 */
@Slf4j
public class CaffeineUserTokenManager implements UserTokenManager {

    private final SecurityProperties securityProperties;

    /**
     * 当前登录的用户信息
     */
    private final Cache<String, CustomUserDetails> userDetailCache;
    /**
     * 当前登录用户的最后一个token,最新在线token
     */
    private final Cache<String, String> currUserTokenCache;
    private final Cache<String, String> captchaCache;

    // 用户黑名单
    private final Cache<String, Long> userBlockCache;

    public CaffeineUserTokenManager(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
        Caffeine<Object, Object> objectObjectCaffeine = Caffeine.newBuilder()
                // 初始容量
//                .initialCapacity(10)
                // 最大缓存数量
//                .maximumSize(-1)
                // 缓存过期时间：写入缓存后，经过某个时间缓存失效
                .expireAfterWrite(securityProperties.getTokenExpiresIn(), TimeUnit.SECONDS)
                // 开启统计功能
                .recordStats();
        this.userDetailCache = objectObjectCaffeine.build();
        this.currUserTokenCache = objectObjectCaffeine.build();
        // 验证码缓存
        this.captchaCache = Caffeine.newBuilder()
                // 缓存过期时间：写入缓存后，经过某个时间缓存失效
                .expireAfterWrite(securityProperties.getCaptchaTimeout(), TimeUnit.SECONDS).build();
        this.userBlockCache = objectObjectCaffeine.build();
    }

    @Override
    public TokenDTO createToken(CustomUserDetails customUserDetails) {

        customUserDetails.setToken(IdUtil.fastUUID());

        Long tokenExpiresIn = securityProperties.getTokenExpiresIn();

        // 创建token
        TokenDTO tokenDTO = new TokenDTO(customUserDetails.getUserId(), customUserDetails.getToken(), SecurityConstants.HEADER_UPP_BEARER, tokenExpiresIn, "", customUserDetails);

        // 设置用户的过期时间
        customUserDetails.setExpiresAt(customUserDetails.getIssuedAt().plusSeconds(tokenExpiresIn));
        // 用户token 和 用户信息
        userDetailCache.put(SecurityConstants.USER_LOGIN_TOKEN_INFO + customUserDetails.getToken(), customUserDetails);

        // 如果不允许多终端登录 那么需要设置当前登录的token,否则不需要设置
        if (!securityProperties.getSoloLogin()) {
            // 当前的登录用户
            currUserTokenCache.put(SecurityConstants.USER_LOGIN_KEY + customUserDetails.getUserId(), tokenDTO.getAccess_token());
        }
        return tokenDTO;
    }

    @Override
    public TokenDTO createToken(User user) {
        return createToken(new CustomUserDetails(user));
    }


    @Override
    public void offlineUser(String token) {
        if (StrUtil.isEmpty(token)) {
            return;
        }

        CustomUserDetails customUserDetails = userDetailCache.getIfPresent(SecurityConstants.USER_LOGIN_TOKEN_INFO + token);

        if (!Objects.isNull(customUserDetails)) {
            // 清除停用用户的TOKEN
            userDetailCache.invalidate(SecurityConstants.USER_LOGIN_KEY + customUserDetails.getUserId());
            userDetailCache.invalidate(SecurityConstants.USER_LOGIN_TOKEN_INFO + token);
        }
    }

    @Override
    public void offlineUser(Long userId) {
        userBlockCache.put(String.valueOf(userId), userId);
    }

    @Override
    public void createCaptchaCode(String captchaId, String captchaCode) {
        log.info("创建验证码:{}-{}", captchaId, captchaCode);
        captchaCache.put(captchaId, captchaCode);
    }

    @Override
    public String getCaptchaCode(String captchaId) {
        return captchaCache.getIfPresent(captchaId);
    }

    @Override
    public AbstractAuthenticationToken checkLoginUserTokenA(String token) {
        CustomUserDetails customUserDetails = this.checkLoginUserTokenGet(token);
        return new CustomAuthenticationToken(token, customUserDetails, customUserDetails.getAuthorities());
    }

    private CustomUserDetails checkLoginUserTokenGet(String token) {
        CustomUserDetails customUserDetails = userDetailCache.getIfPresent(SecurityConstants.USER_LOGIN_TOKEN_INFO + token);
        if (Objects.isNull(customUserDetails)) {
            throw new LoginUserNotFoundException("用户已过期,请重新登录");
        }
        Long blockUserId = userBlockCache.getIfPresent(String.valueOf(customUserDetails.getUserId()));
        if (ObjectUtil.isNotNull(blockUserId)) {
            // 删除黑名单
            userBlockCache.invalidate(String.valueOf(blockUserId));
            // 删除用户信息
            userDetailCache.invalidate(SecurityConstants.USER_LOGIN_TOKEN_INFO + token);

            throw new LoginUserNotFoundException("用户已过期,请重新登录");
        }

        // 校验多终端登录
        if (!securityProperties.getSoloLogin()) {

            String redisOldToken = currUserTokenCache.getIfPresent(SecurityConstants.USER_LOGIN_KEY + customUserDetails.getUserId());

            // 如果当前token 和redis的token不一致 说明被挤下线了
            if (!token.equals(redisOldToken)) {
                userDetailCache.invalidate(SecurityConstants.USER_LOGIN_TOKEN_INFO + token);
                throw new SoloLoginException("您已在其他地方登录");
            }
        }

        // 是否自动续期
        if (securityProperties.getTokenRenewal()) {
            this.autoRenewalToken(customUserDetails, token);
        }

        return customUserDetails;
    }


    /**
     * 用户自动续期
     *
     * @param customUserDetails 用户信息
     * @param token             token
     */
    private void autoRenewalToken(CustomUserDetails customUserDetails, String token) {
        Instant expiresAt = customUserDetails.getExpiresAt();
        Instant instant = expiresAt.plusSeconds(-1800);
        if (ObjectUtil.isNull(expiresAt) || Instant.now().isAfter(instant)) {
            Long tokenExpiresIn = securityProperties.getTokenExpiresIn();
            // 重新设置用户过期时间
            customUserDetails.setExpiresAt(Instant.now().plusSeconds(tokenExpiresIn));

            userDetailCache.put(SecurityConstants.USER_LOGIN_TOKEN_INFO + token, customUserDetails);
            // 当前的登录用户
            currUserTokenCache.put(SecurityConstants.USER_LOGIN_KEY + customUserDetails.getUserId(), token);
        }
    }
}
